/*
    jME Terra package. A Terrain Engine for the Java Monkey Engine.
    Copyright (C) 2005-2006,  Tijl Houtbeckers
    
    **** LICENSE: ****

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License as published by the Free Software Foundation,
	version 2.1.
	
	This library is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
	Lesser General Public License for more details.
	
	You should have received a copy of the GNU Lesser General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

	**** CONTACT: ****

    I can be reached through the jmonkeyengine.com forums (user "llama"), 
    or the jmonkeyengine.com contact page:
    jmonkeyengine.com/index.php?option=com_contact&task=view&contact_id=5           

*/

/**
 * @author Tijl Houtbeckers
 */

package org.llama.jmex.terra;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;

import org.llama.jmex.task.TaskManager;
import org.llama.jmex.terra.pass.TerraViewPass;

import com.jme.renderer.Camera;
import com.jme.scene.Node;
import com.jme.scene.Spatial;

public class TerraView extends Node {
	private static final long serialVersionUID = 1L;

	private TerraManager tm;
	private long updatefreq, lastupdate;
	private Camera cam;
	private TerraViewProperties terraProperties;
	private TaskManager taskManager;
	private LinkedHashSet terraNodes = new LinkedHashSet();
	private LinkedHashSet current = new LinkedHashSet();
	private int maxpasses = 0;

	private HashMap passesSet = new HashMap();
	
	public TerraView(String name, Camera cam, TerraManager tm, int updatefreq, TerraViewProperties terraProperties,
			TaskManager taskManager) {
		super(name);
		this.terraProperties = terraProperties;
		this.cam = cam;
		this.tm = tm;
		this.updatefreq = updatefreq;
		this.taskManager = taskManager;
	}

	protected TerraViewPass[] getPassArray(int passnumber, String passtype, boolean create) {
		TerraViewPass[] tvp = (TerraViewPass[]) passesSet.get(passnumber + "_" + passtype);
		if (create && tvp == null) {
			tvp = new TerraViewPass[0];
			passesSet.put(passnumber + "_" + passtype, tvp);
		}
		return tvp;
	}

	public void addPass(TerraViewPass tvpass, int passnumber, String passtype) {
		TerraViewPass[] tvp = getPassArray(passnumber, passtype, true);
		TerraViewPass[] newtvp = new TerraViewPass[tvp.length + 1];
		System.arraycopy(tvp, 0, newtvp, 0, tvp.length);
		newtvp[tvp.length] = tvpass;
		passesSet.put(passnumber + "_" + passtype, newtvp);
		if (passnumber > maxpasses)
			maxpasses = passnumber;
	}

	public int currentx = 0, currenty = 0;

	private int dir = 0, maxsteps = 0, step = 0;
	private boolean clockwise = true;

	private final static int RIGHT = 1;
	private final static int DOWN = 2;
	private final static int LEFT = 3;
	private final static int UP = 4;
	private int orientation = RIGHT;

	private void resetCounter(int x, int y, int orientation, boolean clockwise) {
		this.currentx = x;
		this.currenty = y;
		this.orientation = orientation;
		this.clockwise = clockwise;
		dir = 0;
		maxsteps = 1;
		step = 0;
	}

	private void next() {
		if (step < maxsteps) {
			switch (dir) {
			case RIGHT:
				currentx++;
				break;
			case LEFT:
				currentx--;
				break;
			case DOWN:
				currenty++;
				break;
			case UP:
				currenty--;
				break;
			}
		}
		if (dir == 0) {
			dir = orientation;
			return;
		}

		if (++step >= maxsteps) {
			step = 0;

			if (clockwise)
				if (++dir > UP)
					dir = RIGHT;
			if (!clockwise)
				if (--dir < RIGHT)
					dir = UP;

			// our direction or the opposite direction
			if ((orientation == dir) || (Math.abs(orientation - dir) == 2)) {
				maxsteps++;
			}
		}

	}

	private transient boolean alwaysupdate = false;

	public void updateNextFrame() {
		alwaysupdate = true;
	}

	public transient int lastx, lasty, lasth;

	public final static String PASS_CURRENT = "current_pre";
	public final static String PASS_MAP = "map_middle";
	public final static String PASS_BLOCK = "block_middle";
	public final static String PASS_ADD = "block_add";

        protected Spatial wrapTerraMesh(TerraMesh toWrap){
            return toWrap;
        }
        
	public void update() {

		synchronized (terraNodes) {
			if (!terraNodes.isEmpty()) {
				TerraData tdata = (TerraData) terraNodes.iterator().next();
				while (terraNodes.remove(tdata));
				if (tdata.getTerraMesh() == null) {
					TerraMesh tmesh = tdata.createTerraMesh();					
					tdata.deleted = true;
					//tdata.setTerraMesh(tmesh);
					
                                        Spatial wrapped = wrapTerraMesh(tmesh);
					attachChild(wrapped);
					wrapped.updateRenderState();
					wrapped.updateWorldVectors();
					
					for (int pass = 0; pass <= maxpasses; pass++) {
						TerraViewPass[] passes = getPassArray(pass, PASS_ADD, false);
						if (passes != null && passes.length > 0) {
							for (int k = 0; k < passes.length; k++)
								passes[k].pass(tdata.xy, this, TerraView.PASS_ADD, pass);
						}
					}
				}
			}
		}

		if (!alwaysupdate && lastupdate + updatefreq > System.currentTimeMillis()) {
			return;
		}
		// TODO: Will be moved to a splitable task
		// TODO: scaling system, translation

		alwaysupdate = false;
		lastupdate = System.currentTimeMillis();

		int x = (int) (cam.getLocation().x / terraProperties.terraScale.x);
		int y = (int) (cam.getLocation().z / terraProperties.terraScale.z);
		lasth = (int) (cam.getLocation().y);
		lastx = x;
		lasty = y;

		{
			synchronized (this) {

				for (int pass = 0; pass <= maxpasses; pass++) {
					TerraViewPass[] passes = getPassArray(pass, PASS_CURRENT, false);
					if (passes != null && passes.length > 0) {

						Iterator it = current.iterator();

						while (it.hasNext()) {
							XYKey xy = (XYKey) it.next();
							boolean keep = true;
							TerraViewPass tvpass;

							for (int k = 0; k < passes.length; k++) {
								if ((tvpass = passes[k]) != null) {
									if (tvpass.getKeyFilter() == null || tvpass.getKeyFilter().equals(xy.key)) {

										if (tvpass.pass(xy, this, PASS_CURRENT, k)) {
											keep = false;
										}
									}
								}
							}
							if (!keep) {
								it.remove();
								remove(xy);
								tm.remove(xy);
							}

						}
					}
				}
			}
			synchronized (this) {

				for (int pass = 0; pass <= maxpasses; pass++) {
					TerraViewPass[] passes = getPassArray(pass, PASS_MAP, false);
					if (passes != null && passes.length > 0) {
						for (int k = 0; k < passes.length; k++)
							update(x, y, false, passes[k], PASS_MAP, pass);
					}
				}

				for (int pass = 0; pass <= maxpasses; pass++) {
					TerraViewPass[] passes = getPassArray(pass, PASS_BLOCK, false);
					if (passes != null && passes.length > 0) {
						for (int k = 0; k < passes.length; k++)
							update(x, y, true, passes[k], PASS_BLOCK, pass);
					}
				}
			}

			taskManager.sortTasks();
		}

	}

	public synchronized void add(XYKey xy) {
		current.add(xy);
	}

	public synchronized void remove(XYKey xy) {
		current.remove(xy);
	}

	public long getPriority(XYKey xy) {
		if (xy.key.equals(TerraManager.K_TD))
			return tm.distanceToBlockSquared(xy.x, xy.y, lastx, lasty);
		return tm.distanceToMapSquared(xy.x, xy.y, lastx, lasty);
	}

	public int startx, starty;

	void update(int x, int y, boolean block, TerraViewPass pass, String passtype, int passnum) {

		if (!block) {
			startx = tm.findMap(x);
			starty = tm.findMap(y);
		} else {
			startx = tm.findBlock(x);
			starty = tm.findBlock(y);
		}

		// todo: find right starting direction from cam
		resetCounter(startx, starty, RIGHT, true);

		boolean going = true;
		XYKey xy = new XYKey(0, 0, null);
		while (going) {
			next();
			xy.x = currentx;
			xy.y = currenty;
			xy.key = null;

			going = pass.pass(xy, this, passtype, passnum);

		}

	}

	public TerraManager getTerraManager() {
		return tm;
	}

	public TaskManager getTaskManager() {
		return taskManager;
	}

	public LinkedHashSet getTerraNodes() {
		return terraNodes;
	}

	public TerraViewProperties getTerraProperties() {
		return terraProperties;
	}

	public Camera getCam() {
		return cam;
	}

	public void setCam(Camera cam) {
		this.cam = cam;
	}

}